﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;

public class MyTcpAcceptConn
{
    private const bool Log_Enable = false;

    private readonly MyTcpServerConn m_ServerConn;
    private readonly int m_Id;
    private Socket m_Socket;
    private readonly Queue<SocketHelper.ThreadMessage> m_ThreadMessageQueue;
    private bool m_IsDispose;

    private long m_CreateTime;
    private long m_LastRecvTime;
    private object m_Obj;
    private bool m_IsDisconnect;

    private readonly byte[] m_RecvBuff = new byte[8 * 1024];
    private int m_NotReadLen;

    private byte[] m_SendBytes;
    private int m_SendLen;

    private readonly Queue<string> m_SendQueue = new Queue<string>();

    public MyTcpAcceptConn(MyTcpServerConn serverConn, int id, Socket so, Queue<SocketHelper.ThreadMessage> msgQueue)
    {
        m_ServerConn = serverConn;
        m_Id = id;
        m_Socket = so;
        m_ThreadMessageQueue = msgQueue;
        m_CreateTime = DateTime.Now.Ticks;

        so.BeginReceive(m_RecvBuff, 0, m_RecvBuff.Length, 0, OnBeginReceiveResult, so);
    }

    public int Id { get { return m_Id; } }

    public MyTcpServerConn ServerConn { get { return m_ServerConn; } }

    public object Obj
    {
        get { return m_Obj; }
        set { m_Obj = value; }
    }

    public long LastRecvTime { get { return m_LastRecvTime; } }

    public void Close()
    {
        if (m_IsDispose) return;
        m_IsDispose = true;

        var so = m_Socket;
        m_Socket = null;
        SocketHelper.ShutdownAndClose(so);
        m_IsDisconnect = true;
    }

    public bool IsConnected
    {
        get { return false == m_IsDisconnect || m_IsDispose; }
    }

    public string AddressInfo
    {
        get
        {
            var so = m_Socket;
            if (null != so)
                return so.LocalEndPoint.ToString();
            return "";
        }
    }

    public void Send(string str)
    {
        if (m_IsDisconnect || m_IsDispose)
        {
            Debug.LogWarning($"acceptConn: Send:disconnect or dispose");
            return;
        }

        lock (m_SendQueue)
        {
            m_SendQueue.Enqueue(str);
            if (m_SendQueue.Count > 1) //消费者还没消费完
                return;
        }

        var so = m_Socket; //用局部变量, 防止执行到这边时, 异步线程设为null了
        if (null != so)
            NextSend(so, str);
    }

    //******************** 异步线程

    private void OnBeginReceiveResult(IAsyncResult ar)
    {
        var socket = (Socket)ar.AsyncState;
        try
        {
            int recvLen = socket.EndReceive(ar);
            m_LastRecvTime = DateTime.Now.Ticks;
            if (recvLen <= 0) //FIN
            {
                //todo: 中断连接, 比如客户端调用Shutdown
                Debug.Log($"acceptConn: FIN: local:{socket.LocalEndPoint}, remote:{socket.RemoteEndPoint}");
                ClientDisconnect();
            }
            else
            {
                m_NotReadLen += recvLen;

                int readedLen = 0;
                while (SocketHelper.IsPacketReady(m_RecvBuff, readedLen, m_NotReadLen, out var bodyLen))
                {
                    var str = Encoding.UTF8.GetString(m_RecvBuff, readedLen + SocketHelper.Packet_Head_Len, bodyLen);
                    if (Log_Enable) Debug.Log($"acceptConn: str:{str}, tid:{Thread.CurrentThread.ManagedThreadId}");

                    var msg = new SocketHelper.ThreadMessage();
                    msg.m_Cmd = "Server.ClientPacket";
                    msg.m_Obj = this;
                    msg.m_Content = str;
                    EnqueueMessage(msg);

                    m_NotReadLen -= SocketHelper.Packet_Head_Len;
                    m_NotReadLen -= bodyLen;

                    readedLen += SocketHelper.Packet_Head_Len;
                    readedLen += bodyLen;
                }

                if (m_NotReadLen > 0)
                {
                    if (Log_Enable) Debug.Log($"acceptConn: nextPacketBytes:{readedLen}~{readedLen + m_NotReadLen}, local:{socket.LocalEndPoint}, remote:{socket.RemoteEndPoint}");
                    Buffer.BlockCopy(m_RecvBuff, readedLen, m_RecvBuff, 0, m_NotReadLen);
                }

                int buffRemainLen = m_RecvBuff.Length - m_NotReadLen;
                socket.BeginReceive(m_RecvBuff, m_NotReadLen, buffRemainLen, 0, OnBeginReceiveResult, socket); //nextCycle
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"acceptConn: connected:{socket.Connected}, {ex.GetType().Name}, {ex.Message}");

            ClientDisconnect();
        }
    }

    private void OnBeginSendResult(IAsyncResult ar)
    {
        var socket = (Socket)ar.AsyncState;
        try
        {
            int sendLen = socket.EndSend(ar);
            if (sendLen <= 0)
            {
                //todo: 数据发不出去(可能是已断开)
                Debug.LogError($"acceptConn: sendLen <= 0: connected:{socket.Connected}, local:{socket.LocalEndPoint}, remote:{socket.RemoteEndPoint}");
                return;
            }

            m_SendLen += sendLen;
            int remainSendLen = m_SendBytes.Length - m_SendLen;
            if (remainSendLen > 0)
            {
                if (Log_Enable) Debug.Log($"acceptConn: sendPart: remain:{remainSendLen}, local:{socket.LocalEndPoint}, remote:{socket.RemoteEndPoint}");
                socket.BeginSend(m_SendBytes, m_SendLen, remainSendLen, 0, OnBeginSendResult, socket);
            }
            else
            {
                string str = null;
                lock (m_SendQueue)
                {
                    m_SendQueue.Dequeue();
                    if (m_SendQueue.Count > 0) //消费一个
                    {
                        if (Log_Enable) Debug.Log($"acceptConn: sendNext: local:{socket.LocalEndPoint}, remote:{socket.RemoteEndPoint}");
                        str = m_SendQueue.Peek();
                    }
                }

                if (null != str)
                    NextSend(socket, str);
                else
                    if (Log_Enable) Debug.Log($"acceptConn: sendEnd: local:{socket.LocalEndPoint}, remote:{socket.RemoteEndPoint}");
            }
        }
        catch (Exception ex)
        {
            //todo: 发送失败
            Debug.LogError($"acceptConn: connected:{socket.Connected}, {ex.GetType().Name}, {ex.Message}");
        }
    }

    
    //********************

    private void NextSend(Socket socket, string str)
    {
        var strBytes = Encoding.UTF8.GetBytes(str);
        int bodyLen = strBytes.Length;
        m_SendBytes = new byte[SocketHelper.Packet_Head_Len + bodyLen]; //todo: 复用, 不要每次新建
        m_SendLen = 0;

        SocketHelper.ConvertInt32To4Bytes(bodyLen, m_SendBytes, 0); //写入消息头
        Buffer.BlockCopy(strBytes, 0, m_SendBytes, SocketHelper.Packet_Head_Len, bodyLen); //copy消息体

        try
        {
            socket.BeginSend(m_SendBytes, m_SendLen, m_SendBytes.Length, 0, OnBeginSendResult, socket);
        }
        catch (Exception ex)
        {
            if (Log_Enable) Debug.LogError($"acceptConn: connected:{socket.Connected}, {ex.GetType().Name}, {ex.Message}");
        }
    }

    private void ClientDisconnect()
    {
        m_IsDisconnect = true;
        if (!m_IsDispose) //~ Close执行完ShutdownAndClose, 然后再执行这句判断: 如果m_IsDispose因为线程变量缓存读到为false就会有问题
        {
            //~ 先执行到这里, 然后切换成Close先ShutdownAndClose, 这边再ShutdownAndClose
            var so = m_Socket;
            m_Socket = null;
            SocketHelper.ShutdownAndClose(so);
            //~ 先执行到这里, 这边先ShutdownAndClose, 然后切换成Close那边再ShutdownAndClose
        }

        //todo: 连接断掉
        var msg = new SocketHelper.ThreadMessage();
        msg.m_Cmd = "Server.ClientDisconnect";
        msg.m_Obj = this;
        EnqueueMessage(msg);

        m_ServerConn.AcceptConnDisconnect(this);
    }

    private void EnqueueMessage(SocketHelper.ThreadMessage msg)
    {
        //渲染线程设为true前, 访问到了, 然后设为true了: 则渲染线程后续会读到失效连接的消息, 要紧吗?
        if (m_IsDispose)
        {
            Debug.Log($"acceptConn: disposed, Message discard: {msg.m_Cmd}");
            return;
        }

        lock (m_ThreadMessageQueue) //渲染线程, accpet线程, recv线程, send线程都会访问
        {
            m_ThreadMessageQueue.Enqueue(msg);
        }
    }


}
